package com.jsy.testlock.lockimpl.redis;

import com.jsy.testlock.entity.MyInteger;
import lombok.SneakyThrows;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.params.SetParams;

import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * @author: SongyangJi
 * @description:
 * @since: 2021/11/29
 */
public class MyRedisLock implements Lock {

    private static final JedisPool jedisPool = new JedisPool("localhost");
    private static final long EXPIRE_TIME_OUT = 1;
    private static final SetParams params = SetParams.setParams().nx().px(EXPIRE_TIME_OUT);

    private final ThreadLocal<String> lockedId = new ThreadLocal<>();

    private final String lockName;


    private MyRedisLock(String lockName) {
        this.lockName = lockName;

    }

    public static MyRedisLock getLock(String lockName) {
        return new MyRedisLock(lockName);
    }


    @SneakyThrows
    @Override
    public void lock() {
        String id = UUID.randomUUID().toString();
        lockedId.set(id);
        try (Jedis jedis = jedisPool.getResource()) {
            for (; ; ) {
                String statusCode = jedis.set(lockName, id, params);
//                System.out.println(statusCode);
                if ("OK".equals(statusCode)) {
//                    System.out.println(Thread.currentThread().getName()+" 加锁 " + id);
                    return;
                }
                Thread.sleep(100); // 可以设计成 wait-notify 不过这里就简陋一点了
            }
        }


    }

    @Override
    public void unlock() {
        while (true) {
            try (Jedis jedis = jedisPool.getResource()) {
                String script =
                        "if redis.call('get',KEYS[1]) == ARGV[1] then" +
                                "   return redis.call('del',KEYS[1]) " +
                                "else" +
                                "   return 0 " +
                                "end";
                String id = lockedId.get();
                String result = jedis.eval(script, Collections.singletonList(lockName), Collections.singletonList(id)).toString();
                if (!"1".equals(result)) {
                    System.err.println("error!!!");
                } else {
//                System.out.println(result);
                    return;
                }
//            System.out.println(Thread.currentThread().getName()+" 解锁 " + id);
            }
        }
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock() {
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    @Override
    public Condition newCondition() {
        return null;
    }


    public static void main(String[] args) {
        MyRedisLock lock = MyRedisLock.getLock("lock1");

//        MyInteger.testLock(lock);


        long l = System.currentTimeMillis();
        final int N = 10000;
        MyInteger integer = new MyInteger(N);
        CountDownLatch countDownLatch = new CountDownLatch(N);
        ExecutorService pool = Executors.newFixedThreadPool(10);
        for (int i = 0; i < N; i++) {
            pool.submit(() -> {
                lock.lock();
                try {
                    integer.decr();
                } finally {
                    lock.unlock();
                }
                countDownLatch.countDown();
            });
        }
        try {
            countDownLatch.await(); // just wait all tasks to finish
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("end : " + integer.getX());
        long r = System.currentTimeMillis();
        System.out.println("耗时 " + (r - l) + " ms");
    }
}
